#include "stdint.h"
#include "global.h"
#include "io.h"
#include "interrupt.h"
#include "print.h"

#define IDT_DESC_CNT 0x30 // 支持的中断数

#define PIC_M_CTRL 0x20 // 主片控制端口
#define PIC_M_DATA 0x21 // 主片数据端口
#define PIC_S_CTRL 0xa0 // 从片控制端口
#define PIC_S_DATA 0xa1 // 从片数据端口


#define EFLAGS_IF 0x00000200 //if标志置1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR))

// 中断门描述符结构体
struct gate_desc {
    uint16_t func_offset_low_word;//中断处理程序偏移量低16位
    uint16_t selector;//中断处理程序段描述符选择子
    uint8_t dcount; //置0

    uint8_t attribute;//属性
    uint16_t func_offset_high_word;//中断处理程序偏移量高16位
};

static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);

//创建中断描述符表（idt） 本质为数组
static struct gate_desc idt[IDT_DESC_CNT];

//中断异常名数组
char* intr_name[IDT_DESC_CNT];

//中断处理函数数组，保存中断处理函数的地址
intr_handler idt_table[IDT_DESC_CNT];

/*声明数组，kernel.asm中的intr_entry_table的数组
  中断处理函数入口
*/
extern intr_handler intr_entry_table[IDT_DESC_CNT];

/**
 * 创建中断门描述符.
 */ 
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) {
    p_gdesc->func_offset_low_word = (uint32_t) function & 0x0000FFFF;
    p_gdesc->selector = SELECTOR_K_CODE;
    p_gdesc->dcount = 0;
    p_gdesc->attribute = attr;
    p_gdesc->func_offset_high_word = ((uint32_t) function & 0xFFFF0000) >> 16;
}

/**
 * 初始化中断描述符表.
 */ 
static void idt_desc_init(void) {
    int i;
    for (i = 0; i < IDT_DESC_CNT; i++) {
        make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
    }
    put_str("idt_desc_init done.\n");
}

// 8259A 初始化
static void pic_init(void) {
    // 初始化主片
    outb(PIC_M_CTRL, 0x11); //icw1:边沿触发，级联，需要icw4
    outb(PIC_M_DATA, 0x20); //icw2：起始中断向量号为0x20

    outb(PIC_M_DATA, 0x04); //icw3：ir2接从片
    outb(PIC_M_DATA, 0x01); //icw4：8086模式，正常eoi

    // 初始化从片
    outb(PIC_S_CTRL, 0x11); //icw1:边沿触发，级联，需要icw4
    outb(PIC_S_DATA, 0x28); //icw2：起始中断向量号为0x28

    outb(PIC_S_DATA, 0x02); //icw3：设置从片连接到主片ir2引脚
    outb(PIC_S_DATA, 0x01); //icw4：8086模式，正常eoi

    // 打开IRQ
    outb(PIC_M_DATA, 0xfc); //主片中断屏蔽
    outb(PIC_S_DATA, 0xff); //从片中断屏蔽

    put_str("pic_init done.\n");
}

/*通用的中断处理函数，一般用在异常出现的时候*/
static void general_intr_handler(uint8_t vec_nr){
    if(vec_nr == 0x27 || vec_nr == 0x2f){
        /*IRQ7,IRQ15会产生伪中断,无需处理
          0x2f是从片8259A上最后一个IRQ引脚，保留项
        */
        return;
    }
    put_str("int vector : 0x");
    put_int(vec_nr);
    put_char('\n');
    /*将光标置0，从屏幕左上角清出一片打印异常信息的区域，方便阅读*/
    set_cursor(0);
    int cursor_pos = 0;
    while(cursor_pos < 320){
        put_char(' ');
        cursor_pos++;
    }
    set_cursor(0); //重置光标为屏幕左上角
    put_str("!!!!!!   excetion message begin  !!!!!!!!\n");
    set_cursor(88);
    put_str(intr_name[vec_nr]);
    if(vec_nr == 14){ //若为Pagefault，将缺失的地址打印出来并悬停
        int page_fault_vaddr = 0;
        //cr2存放造成pagefault的地址
        asm("movl %%cr2, %0" : "=r" (page_fault_vaddr));
        
        put_str("\npage fault addr is ");
        put_int(page_fault_vaddr);
    }
    put_str("\n!!!!!!    excetion message end   !!!!!!!!\n");
    while(1);
}

/*在中断处理程序数组第vector_no个元素中
  注册安装中断处理程序function*/
void register_handler(uint8_t vector_no, intr_handler function){
    /*idt_table数组中的函数是在进入中断后根据中断向量号调用的*/
    idt_table[vector_no] = function;
}

static void exception_init(void){
    int i;
    for(i = 0; i < IDT_DESC_CNT; i++){
        /*将函数地址存入数组idt_table中
          在kernel.asm调用
        */
        idt_table[i] = general_intr_handler;
        intr_name[i] = "unknown";
    }
    intr_name[0] = "#DE Divide Error";
    intr_name[1] = "#DB Debug Exception";
    intr_name[2] = "NMI Interrupt";
    intr_name[3] = "#BP Breakpiont Exception";
    intr_name[4] = "#OF Overflow Exception";
    intr_name[5] = "#BR BOUND Range Exceeded Exception"; 
    intr_name[6] = "#UD Invalid Opcode Exception"; 
    intr_name[7] = "#NM Device Not Available Exception"; 
    intr_name[8] = "#DF Double Fault Exception"; 
    intr_name[9] = "Coprocessor Segment Overrun"; 
    intr_name[10] = "#TS Invalid TSS Exception";
    intr_name[11] = "#NP Segment Not Present"; 
    intr_name[12] = "#SS Stack Fault Exception";
    intr_name[13] = "#GP General Protection Exception"; 
    intr_name[14] = "#PF Page-Fault Exception"; 
    //intr_name[l5 ］第 15 项是 intel 保留项，未使用
    intr_name[16] ="#MF x87 FPU Floating-Point Error";
    intr_name[17] ="#AC Alignment Check Exception";
    intr_name[18] ="#MC Machine-Check Exception"; 
    intr_name[19] ="#XF SIMD Floating-Point Exception";
}

//开中断，并返回开中断前的状态
enum intr_status intr_enable(void){
    enum intr_status old_status;
    if(INTR_ON == intr_get_status()){
        old_status = INTR_ON;
        return old_status;
    } else {
        old_status = INTR_OFF;
        asm volatile("sti");
        return old_status;
    }
}
//关中断，并返回关中断前的状态
enum intr_status intr_disable(void){
    enum intr_status old_status;
    if(INTR_ON == intr_get_status()){
        old_status = INTR_ON;
        asm volatile("cli" : : : "memory");
        return old_status;
    } else {
        old_status = INTR_OFF;
        return old_status;
    }
}
//将中断设置为 status
enum intr_status intr_set_status(enum intr_status status){
    return status & INTR_ON ? intr_enable() : intr_disable();
}
//获取中断当前状态
enum intr_status intr_get_status(void){
    uint32_t eflags = 0;
    GET_EFLAGS(eflags);
    return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}


void idt_init(void) {
    put_str("idt_init start.\n");
    idt_desc_init(); // idt初始化
    exception_init(); //异常初始化
    pic_init(); //8259a初始化

    // 加载idt
    uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t) ((uint32_t) idt << 16)));
    asm volatile ("lidt %0" : : "m" (idt_operand));
    put_str("idt_init done.\n");
}





